{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "## 反向传播算法"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "---"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "#### 介绍"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "反向传播算法是训练神经网络的最常用且最有效的算法。本实验将阐述反向传播算法的基本原理，并用 PyTorch 框架快速的实现该算法。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "#### 知识点"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "- 正向传播算法\n",
    "- 梯度下降算法\n",
    "- 反向传播算法"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "---"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 正向传播算法"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "在讲解反向传播算法之前，让我们先来讲解一下正向传播。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "神经网络的本质其实就是一个非常复杂的且具有大量参数的复合函数，我们将数据作为函数（网络）的输入，将结果作为函数（网络）的输出。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "正向传播算法其实就是通过函数的输入以及神经网络（一个复杂的复合函数），得到函数的输出的过程。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们可以使用一个简单的例子,如下："
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "<img width=\"600px\" src=\"https://doc.shiyanlou.com/courses/2534/1166617/c63891638de6622c9a533647e7fbe6cf-0/wm\n",
    "\">"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "该函数的输入数据为 $x$，参数为 $w$ ，输出为 $loss$。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "则该函数的正向传播过程如下："
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "$$\\hat{y} = x*w $$\n",
    "$$s = y-\\hat{y}$$\n",
    "$$loss = s^2$$"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "上面的步骤就是正向传播的全部过程，我们可以定义正向传播的函数如下："
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "source": [
    "import torch\r\n",
    "\r\n",
    "def forward(x, y, w):\r\n",
    "    # 其中 x,y 为输入数据，w为该函数所需要的参数\r\n",
    "    y_predicted = w * x\r\n",
    "    loss = (y_predicted - y)**2\r\n",
    "    return loss\r\n",
    "\r\n",
    "\r\n",
    "# 测试代码\r\n",
    "x = torch.tensor(1.0)\r\n",
    "y = torch.tensor(2.0)\r\n",
    "w = torch.tensor(1.0, requires_grad=True)\r\n",
    "forward(x, y, w)  # (2-1)²=1"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "tensor(1., grad_fn=<PowBackward0>)"
      ]
     },
     "metadata": {},
     "execution_count": 5
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "import torch\r\n",
    "\r\n",
    "def forward(x,w,y):\r\n",
    "    y_pred = x*w\r\n",
    "    s = y-y_pred\r\n",
    "    loss = s**2\r\n",
    "    return loss\r\n",
    "\r\n",
    "x = torch.tensor(2,dtype=torch.float32)\r\n",
    "w = torch.tensor(2,dtype=torch.float32,requires_grad=True)\r\n",
    "y = torch.tensor(3,dtype=torch.float32)\r\n",
    "# 前向传播就是正向计算一个函数\r\n",
    "print(forward(x,w,y))"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "tensor(1., grad_fn=<PowBackward0>)\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 反向传播"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "反向传播，顾名思义就是正向传播的反向计算。其实反向传播的目的就是计算输出值和参数之间的梯度关系。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "在正向传播中，我们的参数 w 被随机定义为了 1。可以看出，此时的 w 并不能很好地根据 x 值预测出 y 值。我们需要更佳的参数值。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "那么如何获得更佳的参数值呢？我们一般采用的方法就是梯度下降法。该方法在下一个实验中会详细的进行阐述。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "由于我们必须知道参数的偏导才能够使用梯度下降算法，因此，反向传播孕育而生。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "利用反向传播求取函数关于权重的偏导（即梯度），然后根据偏导使用梯度下降算法找到最佳的参数。这个过程其实就是深度学习中模型训练的过程。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "那么如何利用 PyTorch 实现反向传播呢？"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "其实和上一个试验中求取梯度的方法一致，即利用 `loss.backward()` 进行后向传播，求取所要可偏导变量的偏导值："
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "source": [
    "x = torch.tensor(1.0)\r\n",
    "y = torch.tensor(2.0)\r\n",
    "# 将需要求取的 w 设置为可偏导\r\n",
    "w = torch.tensor(1.0, requires_grad=True)\r\n",
    "loss = forward(x, y, w)  # 计算损失\r\n",
    "loss.backward()  # 反向传播，计算梯度\r\n",
    "print(\"此时，loss 关于 w 的偏导为:\", w.grad)\r\n",
    "w.grad.zero_()  # 得到偏导后，清空梯度"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "此时，loss 关于 w 的偏导为: tensor(-2.)\n"
     ]
    },
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "tensor(0.)"
      ]
     },
     "metadata": {},
     "execution_count": 6
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "source": [
    "x = torch.tensor(1.0)\r\n",
    "y = torch.tensor(2.0)\r\n",
    "w = torch.tensor(1.0,requires_grad=True)\r\n",
    "# 前向传播获得结果\r\n",
    "loss  = forward(x,y,w)\r\n",
    "# 反向传播获得梯度\r\n",
    "loss.backward()\r\n",
    "print(\"loss关于w的偏导为\",w.grad)\r\n",
    "w.grad.zero_()"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "loss关于w的偏导为 tensor(-2.)\n"
     ]
    },
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "tensor(0.)"
      ]
     },
     "metadata": {},
     "execution_count": 7
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 实验总结"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "本实验内容较为简洁，主要是对上个实验中的 `loss.backward()` 函数进行详细的讲解。这个函数是所有神经网络模型训练过程中，都会使用到的函数。注意，在进行反向传播和梯度下降后，记得对梯度进行清空，防止梯度累加。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "<hr><div style=\"color: #999; font-size: 12px;\"><i class=\"fa fa-copyright\" aria-hidden=\"true\"> 本课程内容版权归蓝桥云课所有，禁止转载、下载及非法传播。</i></div>"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.8.0 64-bit ('pytorch': conda)"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  },
  "interpreter": {
   "hash": "95edf26445b41d81dc60008cc593bb3c243ca80a3a822915e2b6f7013280bc10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}